#include <iostream>
#include "opcodes.h"

namespace todo { namespace opcode {

#define CASE(name) case name: return #name;

Opcode::~Opcode()
{
	// Cleaning op1
	if (op1.data_type != todo::TODO_RET_VALUE && op1.data) {
		op1.data->type->dealloc(op1.data);
	}
	// Cleaning op2
	if (op2.data_type != todo::TODO_RET_VALUE && op2.data) {
		op2.data->type->dealloc(op2.data);
	}
	// Just delete the result if it's the original one
	if (op1.result && op1.result_flag == todo::NONREFERENCED) {
		delete op1.result;
	}
	if (info) {
		switch (type) {
			case opcode::TODO_FUNC_CALL:
			case opcode::TODO_METHOD_CALL:
			case opcode::TODO_STATIC_METHOD_CALL:
			case opcode::TODO_SLICE:
				((ArgList*)info)->clear();
				delete (ArgList*)info;
				break;
			case opcode::TODO_DECLARE_FUNC:
				((FunctionArgList*)info)->clear();
				delete (FunctionArgList*)info;
				break;
		}
	}
}

static const char* get_opcode_name(OpcodeType type)
{
	switch (type) {
		CASE(TODO_PRINT);
		CASE(TODO_PRINTLN);
		CASE(TODO_METHOD_CALL);
		CASE(TODO_STATIC_METHOD_CALL);
		CASE(TODO_OFFSET);
		CASE(TODO_SLICE);
		CASE(TODO_ASSIGN);
		CASE(TODO_DECLARE_VAR);
		CASE(TODO_PRE_INC);
		CASE(TODO_PRE_DEC);
		CASE(TODO_ASSIGN_MINUS);
		CASE(TODO_MINUS);
		CASE(TODO_ASSIGN_PLUS);
		CASE(TODO_PLUS);
		CASE(TODO_ASSIGN_DIV);
		CASE(TODO_DIV);
		CASE(TODO_ASSIGN_MULT);
		CASE(TODO_MULT);
		CASE(TODO_ASSIGN_MOD);
		CASE(TODO_MOD);
		CASE(TODO_JMP);
		CASE(TODO_JMPZ);
		CASE(TODO_IS_EQUAL);
		CASE(TODO_IS_NOT_EQUAL);
		CASE(TODO_POS_INC);
		CASE(TODO_POS_DEC);
		CASE(TODO_SUBSCRIPT);
		CASE(TODO_GREATER);
		CASE(TODO_LESS);
		CASE(TODO_GREATER_EQUAL);
		CASE(TODO_LESS_EQUAL);
		CASE(TODO_VECTOR);
		CASE(TODO_VECTOR_ADD);
		CASE(TODO_MAP);
		CASE(TODO_MAP_ADD);
		CASE(TODO_DECLARE_FUNC);
		CASE(TODO_FUNC_CALL);
		CASE(TODO_END_FUNC);
		CASE(TODO_RETURN);
		CASE(TODO_EXIT);
		CASE(TODO_XOR);
		CASE(TODO_ASSIGN_XOR);
		CASE(TODO_SL);
		CASE(TODO_ASSIGN_SL);
		CASE(TODO_SR);
		CASE(TODO_ASSIGN_SR);
		CASE(TODO_BW_OR);
		CASE(TODO_ASSIGN_BW_OR);
		CASE(TODO_BW_AND);
		CASE(TODO_ASSIGN_BW_AND);
		CASE(TODO_BREAK);
		default: return "<Unkown>";
	}
}

static const char* get_datatype_name(DataType type)
{
	switch (type) {
		CASE(TODO_UNUSED);
		CASE(TODO_VAR);
		CASE(TODO_CONSTANT);
		CASE(TODO_RET_VALUE);
		default: return "<Unknown>";
	}	
}

static const char* get_resultflag_name(ValueResultFlag flag)
{
	switch (flag) {
		CASE(NONREFERENCED);
		CASE(REFERENCED);
		default: return "<Unknown>";
	}	
}

static const char* get_type_name(const Data& data)
{
	if (data.data_type == TODO_RET_VALUE) {
		return (data.result_flag == todo::NONREFERENCED && data.result && data.result->value) ? data.result->value->type->get_name().c_str() : "NULL";
	} else {
		return data.data ? data.data->type->get_name().c_str() : "NULL";
	}
}

void Opcode::dump(void) const
{
	std::cout << "Opcode[" << op_num << "]: " << get_opcode_name(type) << std::endl;
	std::cout << "Op1: Data type: " << get_datatype_name(op1.data_type) << std::endl;
	std::cout << "     Type: " << get_type_name(op1) << std::endl;
	std::cout << "     Value: " << op1.data << std::endl;
	if (op1.result) {
		std::cout << "     Result: " << op1.result << std::endl;
		std::cout << "     Result flag: " << get_resultflag_name(op1.result_flag) << std::endl;
	}
	std::cout << "     Extra: " << op1.extra << std::endl;
	
	std::cout << "Op2: Data type: " << get_datatype_name(op2.data_type) << std::endl;
	std::cout << "     Type: " << get_type_name(op2) << std::endl;
	std::cout << "     Value: " << op2.data << std::endl;
	if (op2.result) {
		std::cout << "     Result: " << op2.result << std::endl;
		std::cout << "     Result flag: " << get_resultflag_name(op2.result_flag) << std::endl;
	}
	std::cout << "     Extra: " << op2.extra << std::endl;
	
	std::cout << std::endl;
}

}} // todo::opcode
